<?php

function sql_error($msg, $err=""){
	echo("<!-- $msg -->\n");
	if ($err)
	    echo("<!-- Code=$err[code],Message=$err[message] -->");
	exit();
}

class Db{
// public

	// contructor
	function Db ( $host, $database, $user, $password ){
		$this -> host = $host;
		$this -> database = $database;
		$this -> user = $user;
		$this -> password = $password;
	}

	function DbConnect(){
		$this -> link = OCIPLogon ( $this->user, $this->password, $this->database );
		 //приконнектиться не удалось
		if (!$this->link)
		    sql_error("Can't connect to SQL server");
	}

	function OpenCursor ( $query, $cursor_name = "" ){
		if (!$cursor_name)
		    if ($this->cursor_name)
		        $cursor_name = $this->cursor_name;
		    else
		        $cursor_name = "xcur";
	    return new Cursor ($this->link, $query, $cursor_name);
	}

	function ExecProcedure ($query, $ret_param = "", $blob_array = Array()){
		$proc = new Procedure($this->link, $query, $ret_param, $blob_array);
		return $proc->result;
	}

	function ExecFunction ($query, $ret_param = "result", $blob_array = Array()){
		$proc = new Procedure($this->link, $query, $ret_param, $blob_array);
		return $proc->result;
	}

	// returns an object of the Select class
	function Select ( $query ){
		if(!$this -> link)$this->DbConnect();
		return new Select ($this->link, $query);
	}

	// возвращает число изменных записей
	// или -1 в случае ошибки
	function ExecSQL ( $query, $autocommit = true, $blobs = Array(), $clobs=array() ){
	// в массиве BLOBS передается ассоциативный массив с БЛОБ-полями в формате
	// "имя заместителя поля в запросе"=>значение
	// для примера ниже Array("holder1"=>value,....)
	// значения обычных (не БЛОБ) полей передаются прямо в тексте запроса (fld2)

	// исходный запрос (на примере инсерта) "insert into table (fld1,fld2) values (:holder1,val2)"
	// будет заменен на "insert into table (fld1,fld2) values (empty_blob(),val2)
	// returning fld1 into :fld1"
	// если в тексте запроса блобов нет, то запрос не модифицируется и выполняется
	// обычным порядком
	if(!$this -> link)$this->DbConnect();
		// сюда будут складываться имена колонок и переменных для связывания
		$extra_query_vars = "";
		$extra_query_cols = "";
		// формирование строк с БЛОБ колонками и соответствующих им заместителей
		while (list($k, $v) = each($blobs)){
			$query = preg_replace("/:$k\b/","empty_blob()",$query);
			$extra_query_vars .= ",:$k";
			$extra_query_cols .= ",$k";
		}

		// формирование строк с КЛОБ колонками и соответствующих им заместителей
		while (list($k, $v) = each($clobs)){
			$query = preg_replace("/:$k\b/","empty_clob()",$query);
			$extra_query_vars .= ",:$k";
			$extra_query_cols .= ",$k";
		}

		// преобразование запроса
		if ($extra_query_cols && $extra_query_vars)
		    $query .= " returning ".substr($extra_query_cols,1)." into ".substr($extra_query_vars,1);

		// отпарсим
		$stmt = OCIParse( $this->link, $query);

		// создаем для каждого заместителя БЛОБ-поля описатель
		// и связываем его с заместителем в тексте запроса
		reset($blobs);
		$blob_locators = Array();
		while (list($k, $v) = each($blobs)){
			$blob_locators[$k] = OCINewDescriptor($this->link, OCI_D_LOB);
			OCIBindByName ($stmt, ":$k", &$blob_locators[$k], -1, OCI_B_BLOB);
		}

		reset($clobs);
		while (list($k, $v) = each($clobs)){
			$blob_locators[$k] = OCINewDescriptor($this->link, OCI_D_LOB);
			OCIBindByName ($stmt, ":$k", &$blob_locators[$k], -1, OCI_B_CLOB);
		}

		// выполняем
		$res = OCIExecute( $stmt, OCI_DEFAULT );

		// а вот здесь нюанс в случае UPDATE
		// пустыми блобами прописываются ВСЕ записи (потому как это стоит явно в запросе)
		// а вот новое значение пишется только в последнее значение.
		// Но это нестрашно, поскольку блоб все равно пишется в одну запись.
		if ($res){
			// пишем значения БЛОБ-полей в базу
			reset($blobs);
			while (list($k, $v) = each($blobs)){
				if($blobs[$k]!=""){
					$blob_locators[$k]->save($blobs[$k]);
				}
			}
			reset($clobs);
			while (list($k, $v) = each($clobs)){
				if($clobs[$k]!=""){
				    $blob_locators[$k]->save($clobs[$k]);
				}
			}
			// если нужен коммит - делаем его
			if ($autocommit) OCICommit($this->link);
			$res = OCIRowCount($stmt);
			// чистим описатели полей
			reset($blob_locators);
			while (list($k, $v) = each($blob_locators))
		    	OCIFreeDesc($blob_locators[$k]);
		}else{
		    $res = -1;
		}
		OCIFreeStatement($stmt);
		return $res;
	}

	function Rollback(){
		if (!$this->link) sql_error("ExecSQL: Unknown SQL connection<br>SQL=$this->query");
		OCIRollback($this->link);
	}

	function Commit(){
		OCICommit($this->link);
	}

	// returns last error number
	function Errno(){
		$err = OCIError($this->link);
		return $err[code];
	}

	// returns full text of the last error
	function ErrorText(){
		$err = OCIError($this->link);
		return $err[message];
	}

	function GetInsertID(){
    	return 0;
	}

// private

	var $host;
	var $database;
	var $user;
	var $password;
	var $link;
	var $cursor_name;
}


//
class Select{
// public

	var $RowCount = 0;

	function Select ( $link, $query ){
		$this -> link = $link;
		$this -> query = $query;
		$this -> Prepare();
	}

    function RowCount(){
        return 0;
    }

    function Prepare(){
		if($this->link){
		    $this -> statement = OCIParse ( $this -> link, $this->query );
		    if ($this -> statement)
		    	$this->result = OCIExecute($this -> statement);
		    else
		    	sql_error("Select: SQL error<br>SQL=$this->query");
		}else
			sql_error("Select: Unknown SQL connection<br>SQL=$this->query", OCIError($this->link));
    }

	// Fetch a result row as an associative array
	function FetchArray($fetch_lobs = 0){
		if($this->result)
		    OCIFetchInto($this -> statement, &$data, OCI_ASSOC+OCI_RETURN_NULLS+($fetch_lobs?OCI_RETURN_LOBS:0) );
		else
		    sql_error("FetchArray: Unknown SQL result<br>SQL=$this->query", OCIError($this->link));
		$tmp_data = $data;
		$idx = 0;
		if (is_array($data))
			while (list($k,$v) = each($tmp_data)){
			    $data[$idx++] = $v;
			}
			$this->ResultArray = $data;
			return $data;
	}

	function FetchRow ($fetch_lobs = 0){
		if($this->result)
		    OCIFetchInto($this -> statement, &$data, OCI_RETURN_NULLS+($fetch_lobs?OCI_RETURN_LOBS:0) );
		else
		    sql_error("FetchRow: Unknown SQL result<br>SQL=$this->query", OCIError($this->link));
		return $data;
	}

    function FieldNames(){
		$result = array();
	    if($this->result){
			$ncols = OCINumCols($this->statement);
			for ( $i = 1; $i <= $ncols; $i++ )
			    array_push($result, OCIColumnName($this->statement,$i));
        }else{        	sql_error("FieldNames: Unknown SQL result<br>SQL=$this->query");
        }
		return $result;
	}

	function DataSeek($RowNumber){
	}

	function FieldType($FieldNumber){
		return $this->result?OCIColumnType($this->statement, $FieldNumber):0;
	}

	function FreeResult(){
	    if ($this->statement) OCIFreeStatement($this->statement);
	}

// private

	var $link;
	var $cursor;
	var $query;
	var $result;
	var $ResultArray;
}

class Cursor extends Select{

	function Cursor($link,$query,$cursor_name){
		$this -> link = $link;
		$this -> query = $query;
		$this -> cursor_name = $cursor_name;
		$this -> Prepare();
	}

	function Prepare(){
		if($this->link){
			$this->statement = OCIParse ( $this -> link, $this->query );
		    $this->cursor = OCINewCursor( $this -> link );
		    OCIBindByName( $this->statement, "xcur", &$this->cursor, -1, OCI_B_CURSOR );
			if ($this -> statement){
				$this->result = OCIExecute($this -> statement);
				OCIExecute( $this -> cursor );
				$this-> original_stmt = $this -> statement;
				$this -> statement = $this -> cursor;
			}else
			    sql_error("Cursor: SQL error<br>SQL=$this->query");
		}else
		    sql_error("Cursor: Unknown SQL connection<br>SQL=$this->query", OCIError($this->link));
	}

	function FreeResult(){
		if ($this->$original_stmt) {
			OCIFreeStatement( $this -> $original_stmt);
		    OCIFreeCursor( $this -> cursor );
		}
	}

// private

	var $cursor;
	var $original_stmt;
	var $cursor_name;
}

class Procedure extends Select{

	function Procedure($link, $query, $ret_param, $blob_array){
		$this->link = $link;
		$this->query = $query;
		$this->ret_param = $ret_param;
		$this->result = "";
		$this->blob_array = $blob_array;
		$this->Prepare();
	}

	function Prepare(){
		if($this->link){
		    $this->statement = OCIParse ( $this -> link, $this->query );
			if ($this->ret_param){
			    eval("global \$".$this->ret_param."; \$this->result=\$".$this->ret_param.";");
				if (!@OCIBindByName( $this->statement, $this->ret_param, &$this->result, 256 ))
				    sql_error("Procedure: OCIBindByName failed ret_param name=".$this->ret_param."<br>SQL=".$this->query,
				OCIError($this->statement));
			}

			// связать блобы
			if (list($k,$blob_value) = each($this->blob_array)){
				$blob = OCINewDescriptor($this->link, OCI_D_LOB);
				$blob -> WriteTemporary($blob_value);
				OCIFreeDesc($blob);
				if (!OCIBindByName ($this->statement, ":$k", &$blob, -1, OCI_B_BLOB))
				    sql_error("Procedure: OCIBindByName for LOB failed<br>SQL=".$this->query,
				OCIError($this->statement));
			}

			if ($this -> statement){
				if (!@OCIExecute($this -> statement))
				    sql_error("Procedure: OCIExecute failed, SQL=".$this->query,OCIError($this->statement));
		    }else
				sql_error("Procedure: SQL error<br>SQL=$this->query");
		}else
		    sql_error("Procedure: Unknown SQL connection<br>SQL=$this->query", OCIError($this->link));
    }

	var $ret_param;
	var $blob_array;
}

?>